
<!DOCTYPE HTML>
<html lang="zh-hans" >
    <head>
        <meta charset="UTF-8">
        <title>3 模块化 · C++导览 第二版 简体中文版</title>
        <meta http-equiv="X-UA-Compatible" content="IE=edge" />
        <meta name="description" content="">
        <meta name="generator" content="HonKit 5.1.1">
        <meta name="author" content="Windsting">
        
        
    
    <link rel="stylesheet" href="gitbook/style.css">

    
            
                
                <link rel="stylesheet" href="gitbook/@dogatana/honkit-plugin-page-toc-button/plugin.css">
                
            
                
                <link rel="stylesheet" href="gitbook/@dogatana/honkit-plugin-back-to-top-button/plugin.css">
                
            
                
                <link rel="stylesheet" href="gitbook/gitbook-plugin-forkmegithub/plugin.css">
                
            
                
                <link rel="stylesheet" href="gitbook/@honkit/honkit-plugin-highlight/website.css">
                
            
                
                <link rel="stylesheet" href="gitbook/gitbook-plugin-search/search.css">
                
            
                
                <link rel="stylesheet" href="gitbook/gitbook-plugin-fontsettings/website.css">
                
            
        

    

    
        
        <link rel="stylesheet" href="styles/website.css">
        
    
        
    
        
    
        
    
        
    
        
    

        
    
    
    <meta name="HandheldFriendly" content="true"/>
    <meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
    <meta name="apple-mobile-web-app-capable" content="yes">
    <meta name="apple-mobile-web-app-status-bar-style" content="black">
    <link rel="apple-touch-icon-precomposed" sizes="152x152" href="gitbook/images/apple-touch-icon-precomposed-152.png">
    <link rel="shortcut icon" href="gitbook/images/favicon.ico" type="image/x-icon">

    
    <link rel="next" href="ch04.html" />
    
    
    <link rel="prev" href="ch02.html" />
    

    </head>
    <body>
        
<div class="book honkit-cloak">
    <div class="book-summary">
        
            
<div id="book-search-input" role="search">
    <input type="text" placeholder="输入并搜索" />
</div>

            
                <nav role="navigation">
                


<ul class="summary">
    
    
    
        
        <li>
            <a href="https://github.com/windsting/a-tour-of-cpp-2nd-cn" target="_blank" class="custom-link">Github Link</a>
        </li>
    
    

    
    <li class="divider"></li>
    

    
        
        
    
        <li class="chapter " data-level="1.1" data-path="translation_note.html">
            
                <a href="translation_note.html">
            
                    
                    译者言
            
                </a>
            

            
        </li>
    
        <li class="chapter " data-level="1.2" data-path="./">
            
                <a href="./">
            
                    
                    前言
            
                </a>
            

            
        </li>
    
        <li class="chapter " data-level="1.3" data-path="ch01.html">
            
                <a href="ch01.html">
            
                    
                    1 基础知识
            
                </a>
            

            
        </li>
    
        <li class="chapter " data-level="1.4" data-path="ch02.html">
            
                <a href="ch02.html">
            
                    
                    2 用户定义类型
            
                </a>
            

            
        </li>
    
        <li class="chapter active" data-level="1.5" data-path="ch03.html">
            
                <a href="ch03.html">
            
                    
                    3 模块化
            
                </a>
            

            
        </li>
    
        <li class="chapter " data-level="1.6" data-path="ch04.html">
            
                <a href="ch04.html">
            
                    
                    4 类
            
                </a>
            

            
        </li>
    
        <li class="chapter " data-level="1.7" data-path="ch05.html">
            
                <a href="ch05.html">
            
                    
                    5 基本操作
            
                </a>
            

            
        </li>
    
        <li class="chapter " data-level="1.8" data-path="ch06.html">
            
                <a href="ch06.html">
            
                    
                    6 模板
            
                </a>
            

            
        </li>
    
        <li class="chapter " data-level="1.9" data-path="ch07.html">
            
                <a href="ch07.html">
            
                    
                    7 概束和泛型编程
            
                </a>
            

            
        </li>
    
        <li class="chapter " data-level="1.10" data-path="ch08.html">
            
                <a href="ch08.html">
            
                    
                    8 标准库概览
            
                </a>
            

            
        </li>
    
        <li class="chapter " data-level="1.11" data-path="ch09.html">
            
                <a href="ch09.html">
            
                    
                    9 字符串和正则表达式
            
                </a>
            

            
        </li>
    
        <li class="chapter " data-level="1.12" data-path="ch10.html">
            
                <a href="ch10.html">
            
                    
                    10 输入输出
            
                </a>
            

            
        </li>
    
        <li class="chapter " data-level="1.13" data-path="ch11.html">
            
                <a href="ch11.html">
            
                    
                    11 容器
            
                </a>
            

            
        </li>
    
        <li class="chapter " data-level="1.14" data-path="ch12.html">
            
                <a href="ch12.html">
            
                    
                    12 算法
            
                </a>
            

            
        </li>
    
        <li class="chapter " data-level="1.15" data-path="ch13.html">
            
                <a href="ch13.html">
            
                    
                    13 实用功能
            
                </a>
            

            
        </li>
    
        <li class="chapter " data-level="1.16" data-path="ch14.html">
            
                <a href="ch14.html">
            
                    
                    14 数值
            
                </a>
            

            
        </li>
    
        <li class="chapter " data-level="1.17" data-path="ch15.html">
            
                <a href="ch15.html">
            
                    
                    15 并发
            
                </a>
            

            
        </li>
    
        <li class="chapter " data-level="1.18" data-path="ch16.html">
            
                <a href="ch16.html">
            
                    
                    16 历史及兼容性
            
                </a>
            

            
        </li>
    
        <li class="chapter " data-level="1.19" data-path="idx.html">
            
                <a href="idx.html">
            
                    
                    索引
            
                </a>
            

            
        </li>
    

    

    <li class="divider"></li>

    <li>
        <a href="https://github.com/honkit/honkit" target="blank" class="gitbook-link">
            本书使用 HonKit 发布
        </a>
    </li>
</ul>


                </nav>
            
        
    </div>

    <div class="book-body">
        
            <div class="body-inner">
                
                    

<div class="book-header" role="navigation">
    

    <!-- Title -->
    <h1>
        <i class="fa fa-circle-o-notch fa-spin"></i>
        <a href="." >3 模块化</a>
    </h1>
</div>




                    <div class="page-wrapper" tabindex="-1" role="main">
                        <div class="page-inner">
                            
<div id="book-search-results">
    <div class="search-noresults">
    
                                <section class="normal markdown-section">
                                

    
        
                                <p><a class="en-page-number" id="29"></a></p>
<div class="chapter-number"><p class="chapter-number">3</p></div>

<h1 id="modularity">模块化 </h1>
<blockquote>
<p>我打断你的时候不许打断我。</p>
<p>—— 温斯顿·丘吉尔</p>
</blockquote>
<h2 id="3.1">3.1 导言 </h2>
<p>一个C++程序由许多独立开发的部分组成，比如函数（§1.2.1）、
用户定义类型（第2章）、类的层级（§4.5）及模板（第6章）。
管理它们的关键在于给它们之间的交互作出清晰的定义。
第一步也是最重要的一步，是区分接口和实现。
这语言层面上，C++用声明表示接口。
<em>声明（declaration）</em>逐一列举那些用于操控函数或类的事物。例如：</p>
<pre><code class="lang-cpp"><span class="hljs-function"><span class="hljs-type">double</span> <span class="hljs-title">sqrt</span><span class="hljs-params">(<span class="hljs-type">double</span>)</span></span>;    <span class="hljs-comment">// 平方根函数接收double参数，返回double值</span>

<span class="hljs-keyword">class</span> <span class="hljs-title class_">Vector</span> {
<span class="hljs-keyword">public</span>:
    <span class="hljs-built_in">Vector</span>(<span class="hljs-type">int</span> s);
    <span class="hljs-type">double</span>&amp; <span class="hljs-keyword">operator</span>[](<span class="hljs-type">int</span> i);
    <span class="hljs-function"><span class="hljs-type">int</span> <span class="hljs-title">size</span><span class="hljs-params">()</span></span>;
</code></pre>
<p><a class="en-page-number" id="30"></a></p>
<pre><code class="lang-cpp"><span class="hljs-keyword">private</span>:
    <span class="hljs-type">double</span>* elem;   <span class="hljs-comment">// elem指向一个数组，该数组承载sz个double</span>
    <span class="hljs-type">int</span> sz;
};
</code></pre>
<p>这里的重点是函数体，即函数<em>定义（definition）</em>，位于“别的地方”。
对于这个示例，可能会有需求让<code>Vector</code>的数据也位于“别的地方”，
但我们得到后面（抽象类型；§4.3）处理这个问题。
<code>sqrt()</code>的定义可能是这样：</p>
<pre><code class="lang-cpp"><span class="hljs-function"><span class="hljs-type">double</span> <span class="hljs-title">sqrt</span><span class="hljs-params">(<span class="hljs-type">double</span> d)</span>   <span class="hljs-comment">// sqrt()的定义</span>
</span>{
    <span class="hljs-comment">// ... 数学课本里的算法 ...</span>
}
</code></pre>
<p>对于<code>Vector</code>，我们需要给它的三个成员函数都提供定义：</p>
<pre><code class="lang-cpp">Vector::<span class="hljs-built_in">Vector</span>(<span class="hljs-type">int</span> s)               <span class="hljs-comment">// 构造函数的定义</span>
    :elem{<span class="hljs-keyword">new</span> <span class="hljs-type">double</span>[s]}, sz{s}     <span class="hljs-comment">// 初始化成员变量</span>
{
}

<span class="hljs-type">double</span>&amp; Vector::<span class="hljs-keyword">operator</span>[](<span class="hljs-type">int</span> i)   <span class="hljs-comment">// 取下标运算符的定义</span>
{
    <span class="hljs-keyword">return</span> elem[i];
}

<span class="hljs-function"><span class="hljs-type">int</span> <span class="hljs-title">Vector::size</span><span class="hljs-params">()</span>                  <span class="hljs-comment">// size()的定义</span>
</span>{
    <span class="hljs-keyword">return</span> sz;
}
</code></pre>
<p><code>Vector</code>的函数必须提供定义，<code>sqrt()</code>就不必，因为它隶属于标准库。
但是二者并无本质差别：程序库仅仅是“我们恰好用到的某些其它代码”，
编写这些代码的语言构件与我们用的语言构件相同。</p>
<p>任何实体都可以具有多个声明，但定义只能有一个。</p>
<h2 id="3.2">3.2 分离编译 </h2>
<p>C++支持分离编译，这种情况下，对于用到的类型和函数，用户仅能见到它们的声明。
这些类型和函数的定义位于独立的源文件中，并且被单独编译。
借助分离编译，可以把程序组织成一系列半独立的代码片段。
这种分离操作可用来缩短编译时间，把逻辑上独立的部件严格分离开（从而减少犯错）。
程序库通常就是一组独立编译的代码片段（比如函数）。</p>
<p>一般来说，我们把定义模块接口的声明部分放在一个文件里的模块中，
并通过文件名表明它的用途，例如：</p>
<p><a class="en-page-number" id="31"></a></p>
<pre><code class="lang-cpp"><span class="hljs-comment">// Vector.h:</span>

<span class="hljs-keyword">class</span> <span class="hljs-title class_">Vector</span> {
<span class="hljs-keyword">public</span>:
    <span class="hljs-built_in">Vector</span>(<span class="hljs-type">int</span> s);
    <span class="hljs-type">double</span>&amp; <span class="hljs-keyword">operator</span>[](<span class="hljs-type">int</span> i);
    <span class="hljs-function"><span class="hljs-type">int</span> <span class="hljs-title">size</span><span class="hljs-params">()</span></span>;
<span class="hljs-keyword">private</span>:
    <span class="hljs-type">double</span>* elem;   <span class="hljs-comment">// elem指向一个数组，该数组承载sz个double</span>
    <span class="hljs-type">int</span> sz;
};
</code></pre>
<p>该声明会被置于名为<code>Vector.h</code>的文件中，然后，用户<em>包含（include）</em>
这个<em>头文件（header file）</em>，以访问该接口。例如：</p>
<pre><code class="lang-cpp"><span class="hljs-comment">// user.cpp:</span>

<span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string">&quot;Vector.h&quot;</span> <span class="hljs-comment">// 获取Vector的接口</span></span>
<span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string">&lt;cmath&gt;</span>    <span class="hljs-comment">// 获取标准库中的数学函数，其中包括sqrt()</span></span>

<span class="hljs-function"><span class="hljs-type">double</span> <span class="hljs-title">sqrt_sum</span><span class="hljs-params">(Vector&amp; v)</span>
</span>{
    <span class="hljs-type">double</span> sum = <span class="hljs-number">0</span>;
    <span class="hljs-keyword">for</span> (<span class="hljs-type">int</span> i=<span class="hljs-number">0</span>; i!=v.<span class="hljs-built_in">size</span>(); ++i)
        sum+=std::<span class="hljs-built_in">sqrt</span>(v[i]);       <span class="hljs-comment">// 平方根的和</span>
    <span class="hljs-keyword">return</span> sum;
}
</code></pre>
<p>为了确保对编译器的一致性，提供<code>Vector</code>实现的
<code>.cpp</code>文件同样会包含提供接口的<code>.h</code>文件：</p>
<pre><code class="lang-cpp"><span class="hljs-comment">// Vector.cpp:</span>

<span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string">&quot;Vector.h&quot;</span>     <span class="hljs-comment">// 获取Vector的接口</span></span>

Vector::<span class="hljs-built_in">Vector</span>(<span class="hljs-type">int</span> s)
    :elem{<span class="hljs-keyword">new</span> <span class="hljs-type">double</span>[s]}, sz{s}     <span class="hljs-comment">// 初始化成员变量</span>
{
}

<span class="hljs-type">double</span>&amp; Vector::<span class="hljs-keyword">operator</span>[](<span class="hljs-type">int</span> i)
{
    <span class="hljs-keyword">return</span> elem[i];
}

<span class="hljs-function"><span class="hljs-type">int</span> <span class="hljs-title">Vector::size</span><span class="hljs-params">()</span>
</span>{
    <span class="hljs-keyword">return</span> sz;
}
</code></pre>
<p><code>user.cpp</code>和<code>Vector.cpp</code>的代码共享<code>Vector.h</code>内的<code>Vector</code>接口信息，</p>
<p><a class="en-page-number" id="32"></a></p>
<p>但除此之外它们是独立的，并可以独立编译。该程序的各部分可图示如下：</p>
<p><img src="img/ch03_01.png" alt="program-fragments"></p>
<p>严格来说，分离编译并非语言自身的问题，而是事关如何最大化利用特定语言实现优势的问题。
无论如何，这在实践中意义重大。
优秀的程序组织方法是：
把程序当作一组明确定义过依赖关系的模块，利用语言特性表达这种模块化的逻辑关系，
再把这种关系通过文件以实体形式暴露出来，以实现高效的分离编译。</p>
<p>可以被独自（包括<code>#include</code>进来的<code>.h</code>文件）编译的<code>.cpp</code>文件，
叫做<em>编译单元（translation unit）</em>。
一个程序可以由数以千计的编译单元组成。</p>
<h2 id="3.3">3.3 模块（C++20） </h2>
<p>使用<code>#include</code>把程序组织成多个部件的方法，很古老、易出错且成本高昂。
如果在101个编译单元中用到<code>#include header.h</code>，
<code>header.h</code>中的文本将被编译器处理101次。
如果在<code>header2.h</code>之前用了<code>#include header1.h</code>，那么<code>header1.h</code>
中的声明和宏可能会影响<code>header2.h</code>里代码的意义。
很明显，这不太理想，实际上自从1972年这个机制被引入C语言以来，
对于成本高和bug多，它就一直负有不可推卸的责任。</p>
<p>寻找在C++中表示物理模块的好方法，终于初见曙光了。
这个语言特性叫<code>module</code>，尚未进入 ISO C++，
但已经是一个 ISO技术细则（Technical Specification）[ModulesTS]，
<span title="在本书翻译的时候，也就是2022年初，`module`已经被纳入 ISO C++，并定义在“INTERNATIONAL STANDARD ISO/IEC 14882:2020”（该标准文件的第6版，出版于2020年12月）的第10章。">
并将成为C++20的一部分</span><sup><a href="#fn_1" id="reffn_1">1</a></sup>。
因为在编译器实现中已经提供了该功能，所以，尽管其细节可能还要变动，
而且可能要等好几年才能广泛用于生产环境，我依然要在这里推荐使用它。
使用<code>#include</code>的老代码，还会“存活”相当长的时间，
因为要更新它们，代价高昂而且非常耗时。</p>
<p>使用<code>module</code>组织§3.2中<code>Vector</code>和<code>sqrt_sum()</code>的示例，方法如下：</p>
<pre><code class="lang-cpp"><span class="hljs-comment">// 文件 Vector.cpp:</span>

<span class="hljs-keyword">module</span>;     <span class="hljs-comment">// 此编译单元将定义一个模块</span>

<span class="hljs-comment">// ... 此处为 Vector 的实现所需的内容 ...</span>
</code></pre>
<p><a class="en-page-number" id="33"></a></p>
<pre><code class="lang-cpp"><span class="hljs-keyword">export</span> <span class="hljs-keyword">module</span> Vector;   <span class="hljs-comment">// 定义名为&quot;Vector&quot;的模块</span>

<span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">Vector</span> {
<span class="hljs-keyword">public</span>:
    <span class="hljs-built_in">Vector</span>(<span class="hljs-type">int</span> s);
    <span class="hljs-type">double</span>&amp; <span class="hljs-keyword">operator</span>[](<span class="hljs-type">int</span> i);
    <span class="hljs-function"><span class="hljs-type">int</span> <span class="hljs-title">size</span><span class="hljs-params">()</span></span>;
<span class="hljs-keyword">private</span>:
    <span class="hljs-type">double</span>* elem;       <span class="hljs-comment">// elem指向一个数组，该数组承载sz个double</span>
    <span class="hljs-type">int</span> sz;
};

Vector::<span class="hljs-built_in">Vector</span>(<span class="hljs-type">int</span> s)
    :elem{<span class="hljs-keyword">new</span> <span class="hljs-type">double</span>[s]}, sz{s}     <span class="hljs-comment">// 初始化成员变量</span>
{
}

<span class="hljs-type">double</span>&amp; Vector::<span class="hljs-keyword">operator</span>[](<span class="hljs-type">int</span> i)
{
    <span class="hljs-keyword">return</span> elem[i];
}

<span class="hljs-function"><span class="hljs-type">int</span> <span class="hljs-title">Vector::size</span><span class="hljs-params">()</span>
</span>{
    <span class="hljs-keyword">return</span> sz;
}

<span class="hljs-function"><span class="hljs-keyword">export</span> <span class="hljs-type">int</span> <span class="hljs-title">size</span><span class="hljs-params">(<span class="hljs-type">const</span> Vector&amp; v)</span> </span>{ <span class="hljs-keyword">return</span> v.<span class="hljs-built_in">size</span>(); }
</code></pre>
<p>以上代码定义了名为<code>Vector</code>的<code>module</code>，
它导出了类<code>Vector</code>和它所有的成员函数，以及非成员函数 <code>size()</code>。</p>
<p>使用该<code>module</code>的方法是：在需要的地方<code>import</code>它。例如：</p>
<pre><code class="lang-cpp"><span class="hljs-comment">// 文件 user.cpp:</span>

<span class="hljs-keyword">import</span> Vector;      <span class="hljs-comment">// 获取Vector的接口</span>
<span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string">&lt;cmath&gt;</span>    <span class="hljs-comment">// 获取标准库中的数学函数，其中包括sqrt()</span></span>

<span class="hljs-function"><span class="hljs-type">double</span> <span class="hljs-title">sqrt_sum</span><span class="hljs-params">(Vector&amp; v)</span>
</span>{
    <span class="hljs-type">double</span> sum = <span class="hljs-number">0</span>;
    <span class="hljs-keyword">for</span> (<span class="hljs-type">int</span> i=<span class="hljs-number">0</span>; i!=v.<span class="hljs-built_in">size</span>(); ++i)
        sum+=std::<span class="hljs-built_in">sqrt</span>(v[i]);       <span class="hljs-comment">// 平方根的和</span>
    <span class="hljs-keyword">return</span> sum;
}
</code></pre>
<p>我本来也可以<code>import</code>标准库中的数学函数，但我用了老式的<code>#include</code>，
就是为了表明新老方式可以混用。
这样混用的形式，对于老代码逐步从<code>#include</code>迁移到<code>import</code>是必不可少的。</p>
<p><a class="en-page-number" id="34"></a></p>
<p>头文件和模块方式的区别不仅仅在语法层面：</p>
<ul>
<li>模块仅编译一次（而非在每个用到的编译单元都处理）</li>
<li>两个模块的<code>import</code>顺序可任意颠倒，而不会影响其意义</li>
<li>如果把什么东西导入到一个模块，该模块的用户无法隐式访问到被导入的东西，
  以至于引起麻烦：<code>import</code>不会跨模块传导。</li>
</ul>
<p>模块会显著影响可维护性编译期的性能。</p>
<h2 id="3.4">3.4 命名空间 </h2>
<p>除了函数(§1.3)、类(§2.3)以及枚举(§2.5)，
C++还提供一个叫<em>命名空间（namespace）</em>的机制，
用来表示某些声明相互依托，以便这些名称不会跟其它名称发生冲突。
比如说，我想弄个复数类型(§4.2.1, §14.4)：</p>
<pre><code class="lang-cpp"><span class="hljs-keyword">namespace</span> My_code {
    <span class="hljs-keyword">class</span> <span class="hljs-title class_">complex</span> {
        <span class="hljs-comment">// ...</span>
    };

    <span class="hljs-function">complex <span class="hljs-title">sqrt</span><span class="hljs-params">(complex)</span></span>;
    <span class="hljs-comment">// ...</span>

    <span class="hljs-function"><span class="hljs-type">int</span> <span class="hljs-title">main</span><span class="hljs-params">()</span></span>;
}

<span class="hljs-function"><span class="hljs-type">int</span> <span class="hljs-title">My_code::main</span><span class="hljs-params">()</span>
</span>{
    complex z {<span class="hljs-number">1</span>,<span class="hljs-number">2</span>};
    <span class="hljs-keyword">auto</span> z2 = <span class="hljs-built_in">sqrt</span>(z);
    std::cout &lt;&lt; <span class="hljs-string">&apos;{&apos;</span> &lt;&lt; z2.<span class="hljs-built_in">real</span>() &lt;&lt; <span class="hljs-string">&apos;,&apos;</span> &lt;&lt; z2.<span class="hljs-built_in">imag</span>() &lt;&lt; <span class="hljs-string">&quot;}\n&quot;</span>;
    <span class="hljs-comment">// ...</span>
}

<span class="hljs-function"><span class="hljs-type">int</span> <span class="hljs-title">main</span><span class="hljs-params">()</span>
</span>{
    <span class="hljs-keyword">return</span> My_code::<span class="hljs-built_in">main</span>();
}
</code></pre>
<p>把我的代码放到命名空间<code>My_code</code>里后，
确保了我定义的名称没有跟标准库命名空间<code>std</code>(§3.4)中的名称产生冲突。
这种谨慎就很明智了，因为标准库确实提供了<code>complex</code>算术(§4.2.1, §14.4)。</p>
<p>想要在另一个命名空间中访问某个名称，最简单的方式就是：
使用命名空间名进行限定（比如<code>std::cout</code>和<code>My_code::main</code>），
“真正的<code>main</code>”定义在全局命名空间里，就是说并非位于某个命名空间、类或者函数的局部。</p>
<p>反复用命名空间名限定名称，很单调且容易分神，
可以用<code>using</code>-声明把名称引入某个作用域：</p>
<p><a class="en-page-number" id="35"></a></p>
<pre><code class="lang-cpp"><span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">my_code</span><span class="hljs-params">(vector&lt;<span class="hljs-type">int</span>&gt;&amp; x, vector&lt;<span class="hljs-type">int</span>&gt;&amp; y)</span>
</span>{
    <span class="hljs-keyword">using</span> std::swap;    <span class="hljs-comment">// 使用标准库中的 swap</span>
    <span class="hljs-comment">// ...</span>
    <span class="hljs-built_in">swap</span>(x,y);          <span class="hljs-comment">// std::swap()</span>
    other::<span class="hljs-built_in">swap</span>(x,y);   <span class="hljs-comment">// 别的 swap()</span>
    <span class="hljs-comment">// ...</span>
}
</code></pre>
<p><code>using</code>-声明 让命名空间中的一个名称在当前作用域可用，
其效果就像该名称声明在当前作用域中一样。
在<code>using std::swap</code>语句之后，就好像<code>swap</code>被声明在<code>my_code()</code>中一模一样。</p>
<p>要访问标准库命名空间中的所有名称，可以用这个<code>using</code>-指令：</p>
<pre><code class="lang-cpp"><span class="hljs-keyword">using</span> <span class="hljs-keyword">namespace</span> std;
</code></pre>
<p>在<code>using</code>-指令所处的作用域内，它提及的命名空间内的所有名称都能以非限定方式访问。
因此在针对<code>std</code>的<code>using</code>-指令后，我们可以直接用<code>cout</code>代替<code>std::cout</code>。
使用<code>using</code>-指令后，我们无法再对该命名空间内的名称进行精细挑选，
因此要慎用这个功能，它通常适用于程序中广泛使用的库（比如<code>std</code>）,
或者在转化某个未使用<code>namespace</code>程序的过渡期。</p>
<p>命名空间主要用于组织较大的程序组件，比如程序库。
在使用多个单独开发的部分组成程序时，它能化简这个过程。</p>
<h2 id="3.5">3.5 错误处理 </h2>
<p>错误处理是个即庞大又复杂的主题，它的关注点和覆盖面超出了语言特性本身，
而牵涉到编程技术和相关工具。
好在C++的某些特性能帮个忙。主要帮手就是类型系统。
我们不是大费周章地使用内置类型（比如<code>char</code>、<code>int</code>及<code>double</code>）
和语句（比如<code>if</code>、<code>while</code>及<code>for</code>）直接构建程序，
而是构建适用于我们应用程序的类型（比如<code>string</code>、<code>map</code>及<code>regex</code>）
和算法（比如<code>sort()</code>、<code>find_if()</code>及<code>draw_all()</code>）。
这些高级别的设施简化了编程，限制了犯错误的几率
（比如说，你不太可能尝试把树遍历算法应用在对话框上），还能帮助编译器捕获错误。
C++的主要语言设施致力于设计并实现出优雅且高效的抽象
（比如，用户定义类型和应用在它们之上的算法）。
这种抽象的一个影响是：在程序运行时，把故障的检测点和处理点相互分离。
当程序增大，尤其是库被广泛应用，错误处理的标准化就愈加重要了。
在程序开发的早期就规划错误处理规划的策略，是个好主意。</p>
<h3 id="3.5.1">3.5.1 异常 </h3>
<p>回到<code>Vector</code>的例子。
对于§2.3中的vector，对元素越界访问（out-of-range）的时候，<em>应当</em>做些什么呢？</p>
<ul>
<li>在这个情形下，<code>Vector</code>的作者不会知道用户的意图
  （一般来说，<code>Vector</code>的作者甚至不知道这个vector跑在哪个程序里。）</li>
</ul>
<p><a class="en-page-number" id="36"></a></p>
<ul>
<li><code>Vector</code>的用户也没办法一直去检测这个问题
  （如果有办法，那这种越界访问压根儿就不该发生。）</li>
</ul>
<p>假设我们想从越界访问的错误中恢复运行，解决方案是：
实现<code>Vector</code>的人检测越界访问的企图，然后把它告知用户。
然后用户就可以采取适当的措施。举例来说：
<code>Vector::operator[]()</code>能检测越界访问的企图，并抛出<code>out_of_range</code>异常：</p>
<pre><code class="lang-cpp"><span class="hljs-type">double</span>&amp; Vector::<span class="hljs-keyword">operator</span>[](<span class="hljs-type">int</span> i)
{
    <span class="hljs-keyword">if</span> (i&lt;<span class="hljs-number">0</span> || <span class="hljs-built_in">size</span>()&lt;=i)
        <span class="hljs-keyword">throw</span> out_of_range{<span class="hljs-string">&quot;Vector::operator[]&quot;</span>};
    <span class="hljs-keyword">return</span> elem[i];
}
</code></pre>
<p><code>throw</code>把控制权转给处理<code>out_of_range</code>异常的代码，
这个代码位在某些函数里，而这些函数直接或间接地调用了<code>Vector::operator[]</code>。
要做到这些，编译器就得<em>展开（unwind）</em>函数调用堆栈，以便回退到调用者的代码环境。
就是说，异常处理机制将根据需要退出作用域和函数，以便回退到有意处理该类异常的调用者，
必要时沿途调用析构函数(§4.2.2)。例如：</p>
<pre><code class="lang-cpp"><span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">f</span><span class="hljs-params">(Vector&amp; v)</span>
</span>{
    <span class="hljs-comment">// ...</span>
    <span class="hljs-keyword">try</span> {   <span class="hljs-comment">// 此处的异常将被下面定义的代码处理</span>
        v[v.<span class="hljs-built_in">size</span>()] = <span class="hljs-number">7</span>;        <span class="hljs-comment">// 试图访问v到末尾之后</span>
    }
    <span class="hljs-built_in">catch</span> (out_of_range&amp; err) { <span class="hljs-comment">// 坏菜了：out_of_range错误</span>
        <span class="hljs-comment">// ... 处理越界错误 ...</span>
        cerr &lt;&lt; err.<span class="hljs-built_in">what</span>() &lt;&lt; <span class="hljs-string">&apos;\n&apos;</span>;
    }
    <span class="hljs-comment">// ...</span>
}
</code></pre>
<p>我们把有意异常处理的代码放到<code>try</code>-代码块里。
给<code>v[v.size()]</code>赋值的企图不会得逞。
因此，会进入<code>catch</code>-子句，里面包含处理<code>out_of_range</code>异常的代码。
<code>out_of_range</code>异常定义在标准库中（<code>&lt;stdexcept&gt;</code>里），
并且实际上已经被标准库里某些容器的访问函数用到了。</p>
<p>我以引用方式捕捉此异常以避免复制，并使用<code>what()</code>函数打印错误信息，
这个信息是在<code>throw</code>-位置放进异常里的。</p>
<p>使用异常处理机制可以让错误处理更简洁、更系统化，也更具可读性。
要确保这一点，就别滥用<code>try</code>-语句。
以简洁和系统化方式实现错误处理的主要技术（被称为
<em>资源请求即初始化（Resource Acquisition Is Initialization; RAII）</em>）
将在 §4.2.2 中细聊。
RAII的大体思路是：让类的构造函数获取正常运作所需的全部资源，
然后让析构函数释放全部资源，这样资源的释放就可以有保障地隐式执行。</p>
<p><a class="en-page-number" id="37"></a></p>
<p>如果一个函数绝对不应该抛出异常，可以用<code>noexcept</code>声明它。例如：</p>
<pre><code class="lang-cpp"><span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">user</span><span class="hljs-params">(<span class="hljs-type">int</span> sz)</span> <span class="hljs-keyword">noexcept</span>
</span>{
    <span class="hljs-function">Vector <span class="hljs-title">v</span><span class="hljs-params">(sz)</span></span>;
    <span class="hljs-built_in">iota</span>(&amp;v[<span class="hljs-number">0</span>],&amp;v[sz],<span class="hljs-number">1</span>); <span class="hljs-comment">// 用1,2,3,4...填充v（详见§14.3）</span>
    <span class="hljs-comment">// ...</span>
}
</code></pre>
<p>万一<code>user()</code>还是抛出了异常，就会立即调用<code>std::terminate()</code>以终止程序。</p>
<h3 id="3.5.2">3.5.2 不变式 </h3>
<p>使用异常来指示越界访问，演示了这样的情形：
函数检验参数，并在不满足基本假设——<em>先决条件（precondition）</em>——时拒绝运行。
如果我们要郑重其事地声明<code>Vector</code>的下标运算符，就应该有些诸如此类的表达：
“索引必须位于区间[<code>0</code>:<code>size()</code>)内”，
而实际上，我们在<code>operator[]()</code>里检查的就是这个。
[<code>a</code>:<code>b</code>)这个标记定义了一个半开区间，就是说，<code>a</code>在这个区间里，而<code>b</code>不在。
在定义函数的时候，应当考虑一下：前置条件是什么；是否要检测它(§3.5.3)。
对绝大多数程序来说，应该对简单的不变式进行测试，请参阅 §3.5.4。</p>
<p>既然<code>operator[]()</code>运算符要操作<code>Vector</code>类型的对象，
那么，如果<code>Vector</code>的成员不具备“合理的”值，这个运算就毫无意义。
确切的说，我们指出了“<code>elem</code>指向承载<code>sz</code>个元素的数组”，但仅仅止步于注释中。
这种为类声称某个假设为真的语句被称为
<em>类的不变式（class invariant）</em>，简称<em>不变式（invariant）</em>。
为类制定不变式（以确保成员函数有的放矢）的职责归构造函数，
而成员函数运行完成之后，要确保不变式依然成立。
不巧的是，我们<code>Vector</code>的构造函数有点虎头蛇尾了。
它出色地为<code>Vector</code>的成员变量完成了初始化，却没留意传入的参数是否合理。
考虑一下这个：</p>
<pre><code class="lang-cpp"><span class="hljs-function">Vector <span class="hljs-title">v</span><span class="hljs-params">(<span class="hljs-number">-27</span>)</span></span>;
</code></pre>
<p>基本上，这就要出事了。</p>
<p>更靠谱的定义是这样的：</p>
<pre><code class="lang-cpp">Vector::<span class="hljs-built_in">Vector</span>(<span class="hljs-type">int</span> s)
{
    <span class="hljs-keyword">if</span> (s&lt;<span class="hljs-number">0</span>)
        <span class="hljs-keyword">throw</span> length_error{<span class="hljs-string">&quot;Vector constructor: negative size&quot;</span>};
    elem = <span class="hljs-keyword">new</span> <span class="hljs-type">double</span>[s];
    sz = s;
}
</code></pre>
<p>我使用标准库里的<code>length_error</code>异常报告“元素数量不是正整数”的问题，
因为标准库也用这个异常报告这类问题。
如果<code>new</code>运算符没找到可分配的内存，将抛出<code>std::bad_alloc</code>。
我们可以这么写：</p>
<p><a class="en-page-number" id="38"></a></p>
<pre><code class="lang-cpp"><span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">test</span><span class="hljs-params">()</span>
</span>{
    <span class="hljs-keyword">try</span> {
        <span class="hljs-function">Vector <span class="hljs-title">v</span><span class="hljs-params">(<span class="hljs-number">-27</span>)</span></span>;
    }
    <span class="hljs-built_in">catch</span> (std::length_error&amp; err) {
        <span class="hljs-comment">// 处理容量为负数的情况</span>
    }
    <span class="hljs-built_in">catch</span> (std::bad_alloc&amp; err) {
        <span class="hljs-comment">// 处理内存耗尽的问题</span>
    }
}
</code></pre>
<p>你可以定义自己的类当作异常使用，可以在检测到异常的地方塞入任意信息，
这些信息会被带到处理异常的地方(§3.5.1)。</p>
<p>一般来说，函数在捕获异常之后就已经没法搞定待处理的任务了。
然后，异常“处理”就意味着低限度的局部资源清理，然后重新抛出该异常。例如：</p>
<pre><code class="lang-cpp"><span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">test</span><span class="hljs-params">()</span>
</span>{
    <span class="hljs-keyword">try</span> {
        <span class="hljs-function">Vector <span class="hljs-title">v</span><span class="hljs-params">(<span class="hljs-number">-27</span>)</span></span>;
    }
    <span class="hljs-built_in">catch</span> (std::length_error&amp;) {    <span class="hljs-comment">// 处理一下，然后重新抛出</span>
        cerr &lt;&lt; <span class="hljs-string">&quot;test failed: length error\n&quot;</span>;
        <span class="hljs-keyword">throw</span>;                      <span class="hljs-comment">// 重新抛出</span>
    }
    <span class="hljs-built_in">catch</span> (std::bad_alloc&amp;) {       <span class="hljs-comment">// 糟！这个程序没法处理内存耗尽的问题</span>
        std::<span class="hljs-built_in">terminate</span>();           <span class="hljs-comment">// 终止程序</span>
    }
}
</code></pre>
<p>在设计良好的代码里，<code>try</code>-代码块并不多见。
请系统化地使用 RAII 技术(§4.2.2，§5.3)以避免滥用<code>try</code>-代码块。</p>
<p>不变式的概念对于类的设计很重要，对于函数的设计，前置条件有类似的作用。不变式</p>
<ul>
<li>帮助我们理解自己到底想要什么</li>
<li>强制我们更加具体；这让我们对代码的正确性更有胜算（当然要在调试和测试之后）。</li>
</ul>
<p>不变式的概念是C++资源管理的基础，
构造函数（第4章）和析构函数为它提供支持(§4.2.2，§13.2)。</p>
<h3 id="3.5.3">3.5.3 错误处理的替代方案 </h3>
<p>对于所有现实世界的软件而言，错误处理都是个主要的问题，
所以就自然而然地存在多种多样的方案。
如果一个错误在函数内被侦测到，并且无法就地进行处理，
那么这个函数就必须想办法跟调用者沟通这个问题。
对此，抛出异常是C++最常规的机制。</p>
<p><a class="en-page-number" id="39"></a></p>
<p>对于某些语言来说，设计异常机制仅仅是提供返回值的替代方案。
C++不在其列：异常机制的设计意图是给未能完成的任务报告故障。
异常机制跟构造函数及析构函数结合起来，
为错误处理和资源管理(§4.2.2, §5.3)提供了一致的框架。
编译器专门做过优化，跟通过异常抛出同样的值相比，返回值的操作成本低廉得多。</p>
<p>抛出一个异常不仅仅是为当前无法处理的错误报告出错这么简单。
对于分派到的任务，函数可以这样说明自己无法执行：</p>
<ul>
<li>抛出一个异常</li>
<li>用特定返回值表示失败</li>
<li>（使用某个函数，比如<code>terminate()</code>、<code>exit()</code>或<code>abort()</code>）终止程序</li>
</ul>
<p>返回某个错误标识（“错误码（error code）”）的情形有：</p>
<ul>
<li>常见且可预期的故障。例如：
  打开文件的请求失败很常见（可能是文件名对应的文件不存在，或者权限不够）</li>
<li>调用层级非常少，有理由认为调用者能处理该故障。</li>
</ul>
<p>抛出异常的情形有：</p>
<ul>
<li>该错误极其罕见，以至于程序员特别容易忽视对它的检查。
  比如，你上次检查<code>pringf()</code>的返回值是什么时候？</li>
<li>直接调用者也没办法处理这个错误。取而代之，必须把它层层退回给最终调用者。
  比如：对于应用程序的内存分配失败和网络丢失问题，
  如果要求每个函数都进行妥善处理，这根本就行不通。</li>
<li>某个应用程序的底层模块里新增的错误类型，而高层模块根本没写过处理它的代码。
  例如：某个应用程序原本是单线程的，改成了多线程，
  或者资源本来是从本地访问，变成了远程访问的网上资源。</li>
<li>错误码根本没有有效的途径返回。
  例如：构造函数根本就没有可供“调用者”检查的返回值。
  特别是在调用多个局部变量的构造函数，或者是构造复杂对象的一部分时，
  基于错误码的资源清理会变得相当复杂。</li>
<li>通过值返回的方式，会搞得很复杂或者代价高昂，
  例如：要同时返回一个值和一个错误标识（比如<code>pair</code>; §13.4.3），
  可能需要采用传出参数、非局部的错误状态标识，或者什么其它弯弯绕儿。</li>
<li>错误必须沿整个调用路径传达到“最终调用者”。
  反复检查错误码就很繁冗、代价高昂，还容易出错。</li>
<li>修复该错误的方案取决于多个函数调用的结果，
  需要在多个函数调用之间维护某个局部状态，还需要复杂的控制结构。</li>
<li>出错的函数是个回调函数（作为参数的函数），
  以至于直接调用者甚至根本不知道调用了什么函数。</li>
<li>要求执行某些“撤回操作”的错误。</li>
</ul>
<p>终止程序的情形有：</p>
<ul>
<li>某种无法修复的错误。
  例如：对于许多——但并非所有——系统，不存在合理的手段解决内存耗尽问题。</li>
<li>所在系统检测到显著错误时，错误处理机制要求重启线程、进程或者机器本身。</li>
</ul>
<p><a class="en-page-number" id="40"></a></p>
<p>要确保程序被终止，一个方法是为函数添加<code>noexcept</code>，
这样该函数实现中任意位置的<code>throw</code>都能触发<code>terminate()</code>。
注意，某些应用程序不接收无条件终止，因此必须采取替代方案。</p>
<p>很遗憾，以上这些情形并非泾渭分明、易于运用。程序的体量和复杂度都有影响。
有时在程序的演化过程里，取舍会发生变化。这有赖于经验。
如果拿不准，尽量用异常，因为它在体量变化时工作良好，
没必要借助外部工具检查去确保所有错误都被处理。</p>
<p>别笃信所有的错误码或者所有的异常都不可取；它们都有明确的用途。
另外，也别相信有关异常处理缓慢的谣言；
相较于复杂或罕见错误情形的妥善处理和对错误码的反复检查，它通常都快得多。</p>
<p>简洁高效利用异常进行错误处理，RAII(§4.2.2, §5.3)至关重要。
充斥着<code>try</code>-块的代码往往体现了错误处理策略中最糟糕的方面，
这种情形应该采用错误码。</p>
<h3 id="3.5.4">3.5.4 契约（contract） </h3>
<p>目前尚无通用和标准的方式书写不变式、前置条件等可选的运行时测试。
一个名为“契约”的机制提交到了C++20[Garcia,2016] [Garcia,2018]。
它的目标是帮助一部分用户把程序做得更稳妥，他们依赖于测试
——进行地毯式的运行时检查——然后在部署的代码里进行最低限度的检查。
在依赖系统化大规模代码检查的组织中，这种方法广泛用于高性能应用程序。</p>
<p>目前只能依赖权宜之计，例如用命令行里的宏控制运行时检查：</p>
<pre><code class="lang-cpp"><span class="hljs-type">double</span>&amp; Vector::<span class="hljs-keyword">operator</span>[](<span class="hljs-type">int</span> i)
{
    <span class="hljs-keyword">if</span> (RANGE_CHECK &amp;&amp; (i&lt;<span class="hljs-number">0</span> || <span class="hljs-built_in">size</span>()&lt;=i))
        <span class="hljs-keyword">throw</span> out_of_range{<span class="hljs-string">&quot;Vector::operator[]&quot;</span>};
    <span class="hljs-keyword">return</span> elem[i];
}
</code></pre>
<p>标准库提供了调试用的宏<code>assert()</code>，以确保某个条件在运行时成立。例如：</p>
<pre><code class="lang-cpp"><span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">f</span><span class="hljs-params">(<span class="hljs-type">const</span> <span class="hljs-type">char</span>* p)</span>
</span>{
    <span class="hljs-built_in">assert</span>(p!=<span class="hljs-literal">nullptr</span>); <span class="hljs-comment">// p绝不能是nullptr</span>
    <span class="hljs-comment">// ...</span>
}
</code></pre>
<p>如果这个<code>assert()</code>条件在“调试模式”不成立，程序将终止。
如果不在调试模式，<code>assert()</code>就不做检查。
这个方法忒糙还不灵活，不过通常也凑合够用了。</p>
<h3 id="3.5.5">3.5.5 静态断言 </h3>
<p>异常给运行时发现的问题报错。如果能在编译时发现错误就该大力推广。
对于绝大多数类型系统、区分用户定义类型的接口那些语言特性而言，这就是意义所在。
最起码，我们可以对编译期已知的大多数属性进行基本检查，
以编译器错误信息的形式汇报不满足需求的情况。例如：</p>
<p><a class="en-page-number" id="41"></a></p>
<pre><code class="lang-cpp"><span class="hljs-built_in">static_assert</span>(<span class="hljs-number">4</span>&lt;=<span class="hljs-built_in">sizeof</span>(<span class="hljs-type">int</span>), <span class="hljs-string">&quot;integers are too small&quot;</span>);    <span class="hljs-comment">// 检查整数容量</span>
</code></pre>
<p>在不满足<code>4&lt;=sizeof(int)</code>时，这段代码输出<code>integers are too small</code>；
就是说在系统里的<code>int</code>不足4个字节时。
我们把这种陈述预期的语句称为<em>断言（assertion）</em>。</p>
<p><code>static_assert</code>机制可用于任意——可以通过常量表达式(§1.6)表示的——情形。例如：</p>
<pre><code class="lang-cpp"><span class="hljs-keyword">constexpr</span> <span class="hljs-type">double</span> C = <span class="hljs-number">299792.458</span>;                    <span class="hljs-comment">// km/s</span>

<span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">f</span><span class="hljs-params">(<span class="hljs-type">double</span> speed)</span>
</span>{
    <span class="hljs-keyword">constexpr</span> <span class="hljs-type">double</span> local_max = <span class="hljs-number">160.0</span>/(<span class="hljs-number">60</span>*<span class="hljs-number">60</span>);         <span class="hljs-comment">// 160 km/h == 160.0/(60*60) km/s</span>

    <span class="hljs-built_in">static_assert</span>(speed&lt;C,<span class="hljs-string">&quot;can&apos;t go that fast&quot;</span>);        <span class="hljs-comment">// 错误：speed必须是常量</span>
    <span class="hljs-built_in">static_assert</span>(local_max&lt;C,<span class="hljs-string">&quot;can&apos;t go that fast&quot;</span>);    <span class="hljs-comment">// OK</span>

    <span class="hljs-comment">// ...</span>
}
</code></pre>
<p>如果<code>A</code>不为<code>true</code>，那么<code>static_assert(A,S)</code>就会把<code>S</code>作为编译器错误信息输出。
如果不想输出特定信息就把<code>S</code>留空，编译器会采用默认信息：</p>
<pre><code class="lang-cpp"><span class="hljs-built_in">static_assert</span>(<span class="hljs-number">4</span>&lt;=<span class="hljs-built_in">sizeof</span>(<span class="hljs-type">int</span>));    <span class="hljs-comment">// 采用默认信息</span>
</code></pre>
<p>默认信息的内容通常是<code>static_assert</code>所在的源码位置，外加一个表示断言谓词的字母。</p>
<p><code>static_assert</code>最重要的用途体现在泛型编程(§7.2, §13.9)
中对用作参数的类型有特定要求时。</p>
<h2 id="3.6">3.6 函数参数和返回值 </h2>
<p>把信息从程序的一个位置向另一个位置传递，最主要且推荐的方法是通过函数调用。
执行功能所需的信息作为参数传入函数，生成的结果以返回值形式传出。例如：</p>
<pre><code class="lang-cpp"><span class="hljs-function"><span class="hljs-type">int</span> <span class="hljs-title">sum</span><span class="hljs-params">(<span class="hljs-type">const</span> vector&lt;<span class="hljs-type">int</span>&gt;&amp; v)</span>
</span>{
    <span class="hljs-type">int</span> s = <span class="hljs-number">0</span>;
    <span class="hljs-keyword">for</span> (<span class="hljs-type">const</span> <span class="hljs-type">int</span> i : v)
        s += i;
    <span class="hljs-keyword">return</span> s;
}

vector fib = {<span class="hljs-number">1</span>,<span class="hljs-number">2</span>,<span class="hljs-number">3</span>,<span class="hljs-number">5</span>,<span class="hljs-number">8</span>,<span class="hljs-number">13</span>,<span class="hljs-number">21</span>};

<span class="hljs-type">int</span> x = <span class="hljs-built_in">sum</span>(fib);       <span class="hljs-comment">// x变成53</span>
</code></pre>
<p><a class="en-page-number" id="42"></a></p>
<p>在函数间传递信息还有其它途径，比如全局变量(§1.5)、
指针和引用参数(§3.6.1)以及类对象（第4章）的共享状态。
全局变量极不推荐，它是恶名昭彰的出错根源；状态应该仅在特定函数中共享，
这些函数要共同实现出良好定义过的抽象（也就是：类的成员函数；§2.3）。</p>
<p>既然函数信息的传入和传出如此重要，就不难想见它们有多种方式。主要涉及：</p>
<ul>
<li>该对象是被复制还是被共享？</li>
<li>如果该对象被共享，是否可变？</li>
<li>如果对象转移了，是否要留下一个“空对象”(§5.2.2)？</li>
</ul>
<p>参数传递和返回值传出的默认行为都是“复制”(§1.9)，某些复制可以隐式优化为转移。</p>
<p>在<code>sum()</code>例子中，作为结果的<code>int</code>是以复制方式传出<code>sum()</code>的，
但对于可能容量巨大的<code>vector</code>，让它以复制方式进入<code>sum()</code>将会低效且毫无意义，
所以参数以引用方式传入（用<code>&amp;</code>标示；§1.7）。</p>
<p><code>sum()</code>无需修改其参数，这种不可变更性通过将<code>vector</code>声明为<code>const</code>(§1.6)来标示，
因此<code>vector</code>通过<code>const</code>-引用传递。</p>
<h3 id="3.6.1">3.6.1 参数传递 </h3>
<p>先研究怎么把值弄进函数里。默认情况下是复制（“传值（pass-by-value）”），
如果想指向调用者环境里的对象，可以通过引用（“传引用（pass-by-reference）”）。
例如：</p>
<pre><code class="lang-cpp"><span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">test</span><span class="hljs-params">(vector&lt;<span class="hljs-type">int</span>&gt; v, vector&lt;<span class="hljs-type">int</span>&gt;&amp; rv)</span> <span class="hljs-comment">// v是传值；rv是传引用</span>
</span>{
    v[<span class="hljs-number">1</span>] = <span class="hljs-number">99</span>;      <span class="hljs-comment">// 修改v（局部变量）</span>
    rv[<span class="hljs-number">2</span>] = <span class="hljs-number">66</span>;     <span class="hljs-comment">// 修改rv引用向的内容</span>
}

<span class="hljs-function"><span class="hljs-type">int</span> <span class="hljs-title">main</span><span class="hljs-params">()</span>
</span>{
    vector fib = {<span class="hljs-number">1</span>,<span class="hljs-number">2</span>,<span class="hljs-number">3</span>,<span class="hljs-number">5</span>,<span class="hljs-number">8</span>,<span class="hljs-number">13</span>,<span class="hljs-number">21</span>};
    <span class="hljs-built_in">test</span>(fib,fib);
    cout &lt;&lt; fib[<span class="hljs-number">1</span>] &lt;&lt; <span class="hljs-string">&apos; &apos;</span> &lt;&lt; fib[<span class="hljs-number">2</span>] &lt;&lt; <span class="hljs-string">&apos;\n&apos;</span>;    <span class="hljs-comment">// 输出2 66</span>
}
</code></pre>
<p>关心性能的时候，通常对小型的值传值而较大的值传引用。
此处的“小”意味着“复制的成本低廉东西”。
具体来说，“小”的含义因机器架构而异，
但是“不超过两或三个指针容量”是个颇受赞扬的指标。</p>
<p>如果出于性能原因选择传引用但无需修改参数，
可以像<code>sum()</code>例中那样，传<code>const</code>(常)引用。
这是迄今为止的优秀代码中的常见情形：它运行快还不易错。</p>
<p>函数参数带有缺省值的情形也很常见；就是说，一个首选值或者最常用的值。
可以使用<em>缺省函数参数（default function argument）</em>进行指定，例如：</p>
<p><a class="en-page-number" id="43"></a></p>
<pre><code class="lang-cpp"><span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">print</span><span class="hljs-params">(<span class="hljs-type">int</span> value, <span class="hljs-type">int</span> base =<span class="hljs-number">10</span>)</span></span>;    <span class="hljs-comment">// 以“base”为基数输出value</span>

<span class="hljs-built_in">print</span>(x,<span class="hljs-number">16</span>);    <span class="hljs-comment">// 十六进制</span>
<span class="hljs-built_in">print</span>(x,<span class="hljs-number">60</span>);    <span class="hljs-comment">// 六十进制（苏美尔人）</span>
<span class="hljs-built_in">print</span>(x);       <span class="hljs-comment">// 使用缺省值：十进制</span>
``

这是写法简化过的重载：

```<span class="hljs-function">cpp
<span class="hljs-type">void</span> <span class="hljs-title">print</span><span class="hljs-params">(<span class="hljs-type">int</span> value, <span class="hljs-type">int</span> base)</span></span>;    <span class="hljs-comment">// 以“base”为基数输出value</span>

<span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">print</span><span class="hljs-params">(<span class="hljs-type">int</span> value)</span>               <span class="hljs-comment">// 以10为基数输出value</span>
</span>{
    <span class="hljs-built_in">print</span>(value,<span class="hljs-number">10</span>);
}
</code></pre>
<h3 id="3.6.2">3.6.2 值返回 </h3>
<p>完成计算之后，需要把结果弄出函数并交回给调用者。
跟参数一样，值返回也默认采用复制方式，并且这对于较小的对象很完美。
只有在把不属于函数局部作用域的东西转给调用者时，才通过“传引用”返回。例如：</p>
<pre><code class="lang-cpp"><span class="hljs-keyword">class</span> <span class="hljs-title class_">Vector</span> {
<span class="hljs-keyword">public</span>:
    <span class="hljs-comment">// ...</span>
    <span class="hljs-type">double</span>&amp; <span class="hljs-keyword">operator</span>[](<span class="hljs-type">int</span> i) { <span class="hljs-keyword">return</span> elem[i]; }   <span class="hljs-comment">// 返回对第i个元素的引用</span>
<span class="hljs-keyword">private</span>:
    <span class="hljs-type">double</span>* elem;       <span class="hljs-comment">// elem指向一个数组，该数组承载sz个double</span>
    <span class="hljs-comment">// ...</span>
};
</code></pre>
<p><code>Vector</code>的第<code>i</code>个元素的存在不依赖于取下标运算符，因此可以返回对它的引用。</p>
<p>另一方面，在函数的返回操作结束后，局部变量就消失了，所以不能返回指向它的指针或引用：</p>
<pre><code class="lang-cpp"><span class="hljs-function"><span class="hljs-type">int</span>&amp; <span class="hljs-title">bad</span><span class="hljs-params">()</span>
</span>{
    <span class="hljs-type">int</span> x;
    <span class="hljs-comment">// ...</span>
    <span class="hljs-keyword">return</span> x;   <span class="hljs-comment">// 糟糕：返回了指向局部变量x的引用</span>
}
</code></pre>
<p>万幸，所有主流C++编译器都能捕获<code>bad()</code>里这个明显的错误。</p>
<p>返回引用或者较“小”类型的值很高效，但是要把大量信息传出函数该怎么办呢？
考虑这个示例：</p>
<p><a class="en-page-number" id="44"></a></p>
<pre><code class="lang-cpp">Matrix <span class="hljs-keyword">operator</span>+(<span class="hljs-type">const</span> Matrix&amp; x, <span class="hljs-type">const</span> Matrix&amp; y)
{
    Matrix res;
    <span class="hljs-comment">// ... 对所有 res[i,j], res[i,j] = x[i,j]+y[i,j] ...</span>
    <span class="hljs-keyword">return</span> res;
}

Matrix m1, m2;
<span class="hljs-comment">// ...</span>
Matrix m3 = m1+m2;  <span class="hljs-comment">// 没有复制</span>
</code></pre>
<p>即便对时下的硬件而言，<code>Matrix</code>可能都<em>非常（very）</em>大，而且复制的代价高昂。
因此我们不进行复制，
而是为<code>Matrix</code>定义一个转移构造函数（move constructor）(§5.2.2)，
从而以低廉的代价把<code>Matrix</code>从<code>operator+()</code>传出。
此处<em>不</em>需要抱残守缺地使用手动内存管理：</p>
<pre><code class="lang-cpp"><span class="hljs-function">Matrix* <span class="hljs-title">add</span><span class="hljs-params">(<span class="hljs-type">const</span> Matrix&amp; x, <span class="hljs-type">const</span> Matrix&amp; y)</span>   <span class="hljs-comment">// 复杂且易错的20世纪风格</span>
</span>{
    Matrix* p = <span class="hljs-keyword">new</span> Matrix;
    <span class="hljs-comment">// ... 对所有的 *p[i,j], *p[i,j] = x[i,j]+y[i,j] ...</span>
    <span class="hljs-keyword">return</span> p;
}

Matrix m1, m2;
<span class="hljs-comment">// ...</span>
Matrix* m3 = <span class="hljs-built_in">add</span>(m1,m2);    <span class="hljs-comment">// 仅复制一个指针</span>
<span class="hljs-comment">// ...</span>
<span class="hljs-keyword">delete</span> m3;                  <span class="hljs-comment">// 这个操作太容易忘记</span>
</code></pre>
<p>很遗憾，通过指针返回大型对象在老式代码里很常见，而且是个难以捕获错误的主要根源。
别写这样的代码。
注意，<code>operator+()</code>跟<code>add()</code>同样高效，但是定义简单、易于使用还不易出错。</p>
<p>如果函数搞不定手上的活儿，它可以抛出异常(§3.5.1)。
这可以避免因为充斥着测试“罕见问题”错误码而把代码搞得一团糟。</p>
<p>函数的返回类型可以从返回值本身推断出来。例如：</p>
<pre><code class="lang-cpp"><span class="hljs-function"><span class="hljs-keyword">auto</span> <span class="hljs-title">mul</span><span class="hljs-params">(<span class="hljs-type">int</span> i, <span class="hljs-type">double</span> d)</span> </span>{ <span class="hljs-keyword">return</span> i*d; } <span class="hljs-comment">// 此处的“auto”意思是“推断返回类型”</span>
</code></pre>
<p>这很方便，尤其对于泛型函数（函数模板（function template）; §6.3.1）以及
lambda表达式(§6.3.3)来说，但是请谨慎采用，因为推导出来的类型会让接口不稳定：
对函数（或lambda表达式）内容的修改，会改变返回类型。</p>
<h3 id="3.6.3">3.6.3 结构化绑定 </h3>
<p>函数只能返回单独的一个值，但这个值可以是包含多个成员的类对象。
这使得我们得以高效地返回多个值。例如：</p>
<pre><code class="lang-cpp"><span class="hljs-keyword">struct</span> <span class="hljs-title class_">Entry</span> {
    string name;
    <span class="hljs-type">int</span> value;
};
</code></pre>
<p><a class="en-page-number" id="45"></a></p>
<pre><code class="lang-cpp"><span class="hljs-function">Entry <span class="hljs-title">read_entry</span><span class="hljs-params">(istream&amp; is)</span>   <span class="hljs-comment">// 很菜的读取函数（更好的版本参看 §10.5）</span>
</span>{
    string s;
    <span class="hljs-type">int</span> i;
    is &gt;&gt; s &gt;&gt; i;
    <span class="hljs-keyword">return</span> {s,i};
}

<span class="hljs-keyword">auto</span> e = <span class="hljs-built_in">read_entry</span>(cin);

cout &lt;&lt; <span class="hljs-string">&quot;{ &quot;</span> &lt;&lt; e.name &lt;&lt; <span class="hljs-string">&quot; , &quot;</span> &lt;&lt; e.value &lt;&lt; <span class="hljs-string">&quot; }\n&quot;</span>;
</code></pre>
<p>此处的<code>{s,i}</code>被用于构建<code>Entry</code>类型的返回值。
与之类似，可以把<code>Entry</code>的成员“拆包”到本地变量里：</p>
<pre><code class="lang-cpp"><span class="hljs-keyword">auto</span> [n,v] = <span class="hljs-built_in">read_entry</span>(is);
cout &lt;&lt; <span class="hljs-string">&quot;{ &quot;</span> &lt;&lt; n &lt;&lt; <span class="hljs-string">&quot; , &quot;</span> &lt;&lt; v &lt;&lt; <span class="hljs-string">&quot; }\n&quot;</span>;
</code></pre>
<p><code>auto [n,v]</code>这句声明了两个局部变量<code>n</code>和<code>v</code>，
它们的类型从<code>read_entry()</code>的返回类型推导出来。
这个给类对象成员命名的机制叫<em>结构化绑定（structured binding）</em>。</p>
<p>考虑以下示例：</p>
<pre><code class="lang-cpp">map&lt;string,<span class="hljs-type">int</span>&gt; m;
<span class="hljs-comment">// ... 填充 m ...</span>
<span class="hljs-keyword">for</span> (<span class="hljs-type">const</span> <span class="hljs-keyword">auto</span> [key,value] : m)
    cout &lt;&lt; <span class="hljs-string">&quot;{&quot;</span> &lt;&lt; key <span class="hljs-string">&quot;,&quot;</span> &lt;&lt; value &lt;&lt; <span class="hljs-string">&quot;}\n&quot;</span>;
</code></pre>
<p>按惯例，可以用<code>const</code>和<code>&amp;</code>限定<code>auto</code>，例如：</p>
<pre><code class="lang-cpp"><span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">incr</span><span class="hljs-params">(map&lt;string,<span class="hljs-type">int</span>&gt;&amp; m)</span> <span class="hljs-comment">// 为m的每个元素自增1</span>
</span>{
    <span class="hljs-keyword">for</span> (<span class="hljs-keyword">auto</span>&amp; [key,value] : m)
        ++value;
}
</code></pre>
<p>在结构化绑定用于不包含私有数据的类时，绑定方式显而易见：
绑定行为定义的名称数量必须跟类里面的非静态数据成员数量相同，
绑定行为中的引入名称按次序对应成员变量。
与显式使用复合对象相比，这种代码的质量并无差异；
使用结构化绑定的主旨在于恰如其分地表达意图。</p>
<p>通过成员函数访问类对象的操作同样可行。例如：</p>
<pre><code class="lang-cpp">complex&lt;<span class="hljs-type">double</span>&gt; z = {<span class="hljs-number">1</span>,<span class="hljs-number">2</span>};
<span class="hljs-keyword">auto</span> [re,im] = z+<span class="hljs-number">2</span>;         <span class="hljs-comment">// re=3; im=2</span>
</code></pre>
<p><code>conplex</code>有两个数据成员，但它的接口包含两个访问函数，
分别是<code>real()</code>和<code>imag()</code>。
把一个<code>complex&lt;double&gt;</code>映射到两个局部变量，比如<code>re</code>和<code>im</code>，
是可行且高效的，但此种技巧不在此书的关注范畴。</p>
<p><a class="en-page-number" id="46"></a></p>
<h2 id="3.7">3.7 忠告 </h2>
<ul>
<li>[1] 注意区分声明（用做接口）和定义（用做实现）；§3.1。</li>
<li>[2] 使用头文件代表接口，以强调逻辑结构；§3.2；[CG: SF.3]。</li>
<li>[3] 在实现函数的源文件里<code>#include</code>它的头文件；§3.2；[CG: SF.5]。</li>
<li>[4] 不要在头文件里定义非内联函数；§3.2；[CG: SF.2]。</li>
<li>[5] （在支持<code>module</code>的地方）以<code>module</code>替代头文件；§3.3。</li>
<li>[6] 使用命名空间表达逻辑结构；§3.4；[CG: SF.20]。</li>
<li>[7] 为代码迁移、基本类库（比如<code>std</code>），或在局部作用域内使用<code>using</code>-指令；
  §3.4；[CG: SF.6] [CG: SF.7]。</li>
<li>[8] 别把<code>using</code>-指令放在头文件里；§3.4；[CG: SF.7]。</li>
<li>[9] 在无法搞定待处理的任务时，抛出异常指出这种情况；§3.5；[CG: E.2]。</li>
<li>[10] 仅在错误处理的情形下使用异常；§3.5.3；[CG: E.3]。</li>
<li>[11] 在直接调用者该处理某个错误的时候，采用错误码；§3.5.3。</li>
<li>[12] 在错误将要穿过大量函数调用层级的时候，抛出异常；§3.5.3。</li>
<li>[13] 拿不定该用异常还是错误码的时候，用异常；§3.5.3。</li>
<li>[14] 在设计早期就确定错误处理的策略；§3.5；[CG: E.12]。</li>
<li>[15] 使用能够反映设计意图的用户定义类型作为异常（而非内置类型）；§3.5.1。</li>
<li>[16] 不要在每个函数中都捕捉所有异常；§3.5；[CG: E.7]。</li>
<li>[17] 用 RAII 代替<code>try</code>-代码块；§3.5.1, §3.5.2；[CG: E.6]。</li>
<li>[18] 如果函数不该抛出异常，用<code>noexcept</code>声明它；§3.5；[CG: E.12]。</li>
<li>[19] 让构造函数建立不变式，如果做不到就抛出异常；§3.5.2；[CG: E.5]。</li>
<li>[20] 围绕不变式设计错误处理策略§3.5.2；[CG: E.4]。</li>
<li>[21] 能在编译期检查的就在编译期检查；§3.5.5 [CG: P.4] [CG: P.5]。</li>
<li>[22] “小”值传值，“大”值传引用；§3.6.1；[CG: F.16]。</li>
<li>[23] 尽可能用常（<code>const</code>）引用而非普通引用；§3.6.1；[CG: F.17]。</li>
<li>[24] 使用函数返回值进行返回（而不要用传出参数）；
  §3.6.2；[CG: F.20] [CG: F.21]。</li>
<li>[25] 别滥用返回类型推断；§3.6.2。</li>
<li>[26] 别滥用结构化绑定；使用具名返回类型可以让文档更清晰；§3.6.3。</li>
</ul>
<blockquote id="fn_1">
<sup>1</sup>. 在本书翻译的时候，也就是2022年初，<code>module</code>已经被纳入 ISO C++，并定义在“INTERNATIONAL STANDARD ISO/IEC 14882:2020”（该标准文件的第6版，出版于2020年12月）的第10章。<a href="#reffn_1" title="Jump back to footnote [1] in the text."> ↩</a>
</blockquote>

<script>console.log(window.location.pathname)</script>
<div id="disqus_thread"></div>
<script>

/**
*  RECOMMENDED CONFIGURATION VARIABLES: EDIT AND UNCOMMENT THE SECTION BELOW TO INSERT DYNAMIC VALUES FROM YOUR PLATFORM OR CMS.
*  LEARN WHY DEFINING THESE VARIABLES IS IMPORTANT: https://disqus.com/admin/universalcode/#configuration-variables*/
/*
var disqus_config = function () {
this.page.url = window.location.href;
this.page.identifier = window.location.pathname;
};
*/
(function() { // DON'T EDIT BELOW THIS LINE
var d = document, s = d.createElement('script');
s.src = 'https://a-tour-of-cpp-2nd-cn.disqus.com/embed.js';
s.setAttribute('data-timestamp', +new Date());
(d.head || d.body).appendChild(s);
})();
</script>
<noscript>Please enable JavaScript to view the <a href="https://disqus.com/?ref_noscript" target="_blank">comments powered by Disqus.</a></noscript>
                                
    

                                </section>
                            
    </div>
    <div class="search-results">
        <div class="has-results">
            
            <h1 class="search-results-title"><span class='search-results-count'></span> results matching "<span class='search-query'></span>"</h1>
            <ul class="search-results-list"></ul>
            
        </div>
        <div class="no-results">
            
            <h1 class="search-results-title">No results matching "<span class='search-query'></span>"</h1>
            
        </div>
    </div>
</div>

                        </div>
                    </div>
                
            </div>

            
                
                <a href="ch02.html" class="navigation navigation-prev " aria-label="Previous page: 2 用户定义类型">
                    <i class="fa fa-angle-left"></i>
                </a>
                
                
                <a href="ch04.html" class="navigation navigation-next " aria-label="Next page: 4 类">
                    <i class="fa fa-angle-right"></i>
                </a>
                
            
        
    </div>

    <script>
        var gitbook = gitbook || [];
        gitbook.push(function() {
            gitbook.page.hasChanged({"page":{"ch":3,"title":"3 模块化","level":"1.5","depth":1,"next":{"title":"4 类","level":"1.6","depth":1,"path":"ch04.md","ref":"ch04.md","articles":[]},"previous":{"title":"2 用户定义类型","level":"1.4","depth":1,"path":"ch02.md","ref":"ch02.md","articles":[]},"dir":"ltr"},"config":{"plugins":["@dogatana/page-toc-button","@dogatana/back-to-top-button","copy-code-button","forkmegithub","disqus-legacy"],"root":"./src","styles":{"website":"styles/website.css","pdf":"styles/pdf.css","epub":"styles/epub.css","mobi":"styles/mobi.css","ebook":"styles/ebook.css","print":"styles/print.css"},"pluginsConfig":{"@dogatana/back-to-top-button":{},"styles":{"website":"styles/website.css"},"search":{},"@dogatana/page-toc-button":{},"lunr":{"maxIndexSize":1000000,"ignoreSpecialCharacters":false},"fontsettings":{"theme":"white","family":"sans","size":2},"highlight":{},"disqus-legacy":{"shortname":"a-tour-of-cpp-2nd-cn"},"copy-code-button":{},"forkmegithub":{"color":"orange","url":"https://github.com/windsting/a-tour-of-cpp-2nd-cn"},"theme-default":{"styles":{"website":"styles/website.css","pdf":"styles/pdf.css","epub":"styles/epub.css","mobi":"styles/mobi.css","ebook":"styles/ebook.css","print":"styles/print.css"},"showLevel":false}},"theme":"default","author":"Windsting","pdf":{"pageNumbers":true,"fontSize":14,"fontFamily":"Arial","paperSize":"a5","chapterMark":"pagebreak","pageBreaksBefore":"/","margin":{"right":62,"left":62,"top":56,"bottom":56},"embedFonts":false},"structure":{"langs":"LANGS.md","readme":"README.md","glossary":"GLOSSARY.md","summary":"SUMMARY.md"},"variables":{},"title":"C++导览 第二版 简体中文版","language":"zh-hans","links":{"sidebar":{"Github Link":"https://github.com/windsting/a-tour-of-cpp-2nd-cn"}},"gitbook":"*","description":"A Tour of C++ (第二版) 非官方中译本"},"file":{"path":"ch03.md","mtime":"2023-02-12T09:23:08.805Z","type":"markdown"},"gitbook":{"version":"5.1.1","time":"2023-10-27T09:54:29.738Z"},"basePath":".","book":{"language":""}});
        });
    </script>
</div>

        
    <noscript>
        <style>
            .honkit-cloak {
                display: block !important;
            }
        </style>
    </noscript>
    <script>
        // Restore sidebar state as critical path for prevent layout shift
        function __init__getSidebarState(defaultValue){
            var baseKey = "";
            var key = baseKey + ":sidebar";
            try {
                var value = localStorage[key];
                if (value === undefined) {
                    return defaultValue;
                }
                var parsed = JSON.parse(value);
                return parsed == null ? defaultValue : parsed;
            } catch (e) {
                return defaultValue;
            }
        }
        function __init__restoreLastSidebarState() {
            var isMobile = window.matchMedia("(max-width: 600px)").matches;
            if (isMobile) {
                // Init last state if not mobile
                return;
            }
            var sidebarState = __init__getSidebarState(true);
            var book = document.querySelector(".book");
            // Show sidebar if it enabled
            if (sidebarState && book) {
                book.classList.add("without-animation", "with-summary");
            }
        }

        try {
            __init__restoreLastSidebarState();
        } finally {
            var book = document.querySelector(".book");
            book.classList.remove("honkit-cloak");
        }
    </script>
    <script src="gitbook/gitbook.js"></script>
    <script src="gitbook/theme.js"></script>
    
        
        <script src="gitbook/@dogatana/honkit-plugin-page-toc-button/plugin.js"></script>
        
    
        
        <script src="gitbook/@dogatana/honkit-plugin-back-to-top-button/plugin.js"></script>
        
    
        
        <script src="gitbook/gitbook-plugin-copy-code-button/toggle.js"></script>
        
    
        
        <script src="gitbook/gitbook-plugin-forkmegithub/plugin.js"></script>
        
    
        
        <script src="gitbook/gitbook-plugin-search/search-engine.js"></script>
        
    
        
        <script src="gitbook/gitbook-plugin-search/search.js"></script>
        
    
        
        <script src="gitbook/gitbook-plugin-lunr/lunr.min.js"></script>
        
    
        
        <script src="gitbook/gitbook-plugin-lunr/search-lunr.js"></script>
        
    
        
        <script src="gitbook/gitbook-plugin-fontsettings/fontsettings.js"></script>
        
    

    </body>
</html>

